package com.ms.service.module.analysisgroup;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ms.game.itembox.AccountItemChangeWay;
import com.ms.game.itembox.ChangeItem;
import com.ms.game.itembox.Item;
import com.ms.game.itembox.ItemChangeParam;
import com.ms.game.itembox.ItemChangeWay;
import com.ms.game.itembox.container.ItemBasic;
import com.ms.game.itembox.container.ItemCrystalCard;
import com.ms.game.itembox.container.ItemGoldCoinCard;
import com.ms.game.itembox.db.AccountItemModel;
import com.ms.player.PlayerUserType;
import com.ms.service.module.analysis.GameNameWithHallType;
import com.ms.service.module.analysisgroup.async.SaveAlertLogModelAsync;
import com.ms.service.module.analysisgroup.async.SaveGameRevenueAndExpenditureLogAsync;
import com.ms.service.module.analysisgroup.async.SaveItemChangeCountAsync;
import com.ms.service.module.analysisgroup.async.SaveUsedCoinCardDetailAsync;
import com.ms.service.module.analysisgroup.bin.GameOperateAlertInfoUtil;
import com.ms.service.module.analysisgroup.bin.GameOperateAlertInfoUtil.ActionType;
import com.ms.service.module.analysisgroup.bin.GameOperateAlertInfoUtil.OperateType;
import com.ms.service.module.analysisgroup.db.AnalysisGroupDAO;
import com.ms.service.module.analysisgroup.db.model.CoinExpenditureAlertSettingModel;
import com.ms.service.module.analysisgroup.db.model.CoinExpenditureLogModel;
import com.ms.service.module.analysisgroup.db.model.GameOperateAlertLogModel;
import com.ms.service.module.analysisgroup.db.model.GameRevenueAndExpenditureAlertLogModel;
import com.ms.service.module.analysisgroup.db.model.GameRevenueAndExpenditureModel;
import com.ms.service.module.analysisgroup.db.model.ItemChangeCountModel;
import com.ms.service.module.analysisgroup.db.model.UsedCoinCardDetailLogModel;
import com.ms.service.module.analysisgroup.message.SCheckSendGiftIntervalTimeData;
import com.ms.service.module.analysisgroup.message.SCoinExpenditureData;
import com.ms.service.module.analysisgroup.message.SGameRnERequestData;
import com.ms.service.module.analysisgroup.message.SItemChangeData;
import com.ms.service.module.analysisgroup.message.SPlayerRnERequestData;
import com.ms.service.module.analysisgroup.message.SUsedCoinCardDetailData;
import com.xpec.c4.game.common.Helper;
import com.xpec.c4.game.datamanager.agent.money.CoinType;
import com.xpec.c4.game.datamanager.agent.money.MoneyChangeParam;
import com.xpec.c4.game.time.TimeSys;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.service.group.transportlayer.GroupMessage;
import com.xpec.c4.service.message.MessageAction;
import com.xpec.c4.service.module.ConsistentGroupName;
import com.xpec.c4.service.module.ModuleName;
import com.xpec.c4.service.module.common.ItemInfoManager;
import com.xpec.c4.service.module.common.PlayerCommonInfoManager;

public class AnalysisGroupProcessor {

	private AnalysisGroupModule module = null;
	
	public AnalysisGroupModule getModule(){
		return module;
	}
	
	public void setModule(AnalysisGroupModule module){
		this.module = module;
	}
	
	/** log object */
	private static final Log log = LogFactory.getLog(AnalysisGroupProcessor.class);

	/**
	 * 遊戲收支暫存
	 */
	private ConcurrentHashMap<GameNameWithHallType, GameRevenueAndExpenditureData> GameREMap = new ConcurrentHashMap<GameNameWithHallType, GameRevenueAndExpenditureData>();

	/**
	 * 使用卡片進行遊戲的orderID
	 */
	private ConcurrentHashMap<GameNameWithHallType, ArrayList<GUID>> useCardBetOrderIDs = new ConcurrentHashMap<GameNameWithHallType, ArrayList<GUID>>();
	
	/**
	 * 玩家收支暫存
	 */
	private ConcurrentHashMap<String, PlayerRevenueAndExpenditureData> PlayerREMap = new ConcurrentHashMap<String, PlayerRevenueAndExpenditureData>();
	
	/**
	 * 各種支出統計
	 */
	private ConcurrentHashMap<CoinExpenditureWay, CoinExpenditureData> CoinExpenditureMap = new ConcurrentHashMap<CoinExpenditureWay, CoinExpenditureData>();
	
	/**
	 * 玩家各種遊戲操作紀錄
	 */
	private ConcurrentHashMap<String, GameOperateData> PlayerOPMap = new ConcurrentHashMap<String, GameOperateData>();
	
	/**
	 * 物品變化紀錄<br>
	 * key: defaultIDWithRefineLV
	 */
	private ConcurrentHashMap<String, ItemChangeData> itemChangeDatas = new ConcurrentHashMap<String, ItemChangeData>();
	
	/**
	 * C幣卡使用細節統計<br>
	 * key: parValue(面額)
	 */
	private ConcurrentHashMap<Integer, UsedCoinCardData> usedCoinCardDetailMap = new ConcurrentHashMap<Integer, UsedCoinCardData>();
	
	/**
	 * 為了在每次寫LOG時可以將0的資訊也寫入<br>
	 * 根據ConsistentGroupName.lookUpByString初始自己module的GameREMAP
	 */
	public void initGameREMap(){
		for(GameNameWithHallType gameNameWithHallType : GameNameWithHallType.values()){
			//不要記錄大廳的
			if(gameNameWithHallType.equals(GameNameWithHallType.LOBBY))
				continue;
			
			//這邊LookUp方式要跟送消息進來的方式一樣
			String moduleLookUp = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, gameNameWithHallType.name());
			//自己module負責的的遊戲才紀錄
			if(getModule().getModuleName().equals(moduleLookUp)){
				GameREMap.put(gameNameWithHallType, new GameRevenueAndExpenditureData());
			}
		}
	}
	
	/**
	 * 從playerManager那邊拿整個MoneyChangeParam，整理收入與支出到analysis group統計
	 */
	public static void OnSendRevenueAndExpenditureToAnalysisGroup(MoneyChangeParam param){
		String gameName = param.getGameId();
		String hallType = param.getGameHallId();
		GUID playerGUID = param.getPlayerId();
		boolean isGameRevenue = false;
		boolean isFormal = false;
		boolean isJP = false;
		BigDecimal changeValue = BigDecimal.ZERO;
		
		//是否是使用卡片的下注額，如果是的話統計會把orderID記起來
		//然後在得分的地方判斷orderID是使用卡片的得分就另外統計
		boolean isUseCardBet = false;
		
		//玩家金錢增加代表遊戲機台支出
		//從PlayerManager取出時已有先判斷是DailyBet or DailyScore
		isGameRevenue = !param.getChangeWay().isAdd();
		
		if(param.getDropItemParam() != null){
			//是得分而且是使用卡片
			if(param.getChangeWay().isDailyBet() && param.getDropItemParam().isUseItemInGame()){
				isUseCardBet = true;
			}
		}

		//如果是JP時
		if(param.getChangeWay().isJackpot()){
			isFormal = true;
			
			//先處理JP部分
			isJP = true;
			changeValue = param.getJpScore();
			sendGameRevenueAndExpenditureToAnalysisGroup(param.getOrderId(), gameName, hallType, isGameRevenue, isFormal, isJP, changeValue, isUseCardBet);
			sendPlayerRevenueAndExpenditureToAnalysisGroup(playerGUID, !isGameRevenue, isJP, changeValue);
			
			//再處理得分部分
			isJP = false;
			changeValue = Helper.sub(param.getChangeCoinLister().getCoinMap().get(CoinType.GOLD).getChangeValue(), param.getJpScore());
			//如果得分部分計算之後是0就不用另外處理，表示獲得金額全額以JP得分計算
			if(Helper.isZero(changeValue))
				return;
			else{
				sendGameRevenueAndExpenditureToAnalysisGroup(param.getOrderId(), gameName, hallType, isGameRevenue, isFormal, isJP, changeValue, isUseCardBet);
				sendPlayerRevenueAndExpenditureToAnalysisGroup(playerGUID, !isGameRevenue, isJP, changeValue);	
			}
		}
		//一般得分、押注
		else {
			//如果ChangeCoinLister裡面沒東西就不用處理(玩家獲得物品時)
			if(param.getChangeCoinLister().getSize() == 0){
				return;
			}
			
			//如果用體驗幣玩
			if(param.getChangeCoinLister().getCoinMap().containsKey(CoinType.TRIAL)){
				changeValue = param.getChangeCoinLister().getCoinMap().get(CoinType.TRIAL).getChangeValue();
			}
			else{
				isFormal = true;
				changeValue = param.getChangeCoinLister().getCoinMap().get(CoinType.GOLD).getChangeValue();
				sendPlayerRevenueAndExpenditureToAnalysisGroup(playerGUID, !isGameRevenue, isJP, changeValue);
			}
			sendGameRevenueAndExpenditureToAnalysisGroup(param.getOrderId(), gameName, hallType, isGameRevenue, isFormal, isJP, changeValue, isUseCardBet);
		}
	}
	
	/**
	 * 送出整理過後的資訊(遊戲館)
	 */
	public synchronized static void sendGameRevenueAndExpenditureToAnalysisGroup
	(
			GUID orderID,
			String gameName,
			String hallType,
			boolean isRevenue,
			boolean isFormal,
			boolean isJP,
			BigDecimal changeValue,
			boolean isCardBet
	)
	{
		SGameRnERequestData data = new SGameRnERequestData();
		data.setOrderID(orderID);
		data.setGameName(gameName);
		data.setHallType(hallType);
		data.setRevenue(isRevenue);
		data.setFormal(isFormal);
		data.setJP(isJP);
		data.setChangeValue(changeValue.toString());
		data.setCardBet(isCardBet);
		
		try {
			String moduleString = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, gameName +"_"+ hallType);
			if(moduleString == null){
				if(log.isErrorEnabled()){
					GameUtility.logErrorException(log, "lookUpByString result null, gameName: "+gameName+" hallType: "+hallType);
				}
				return;
			}
			
			GroupMessage msg = new GroupMessage();
			msg.setDestModuleID(moduleString);
			msg.setMessageContent(data);
			msg.setMessageType(MessageTypes.S_ANALYSIS_GAME_REVENUE_AND_EXPENDITURE);
			MessageAction.sendGroupMessage(msg);
		} catch (Exception e) {
			if(log.isErrorEnabled()){
				log.error(GameUtility.getCurMethodName()+" can't send GroupMessage, moduleName: "+ModuleName.GROUP_ANALYSIS+" gameName: "+gameName+" hallType: "+hallType, e);
			}
			return;
		}

	}
	
	/**
	 * 送出整理過後的資訊(玩家)
	 */
	public synchronized static void sendPlayerRevenueAndExpenditureToAnalysisGroup
	(
			GUID playerGUID,
			boolean isRevenue,
			boolean isJP,
			BigDecimal changeValue
	)
	{
		SPlayerRnERequestData data = new SPlayerRnERequestData();
		data.setPlayerGUID(playerGUID.toString());
		data.setRevenue(isRevenue);
		data.setJP(isJP);
		data.setChangeValue(changeValue.toString());
		
		try {
			String moduleString = ConsistentGroupName.lookUp(ModuleName.GROUP_ANALYSIS, playerGUID);
			if(moduleString == null){
				if(log.isErrorEnabled()){
					GameUtility.logErrorException(log, "lookUp result null, playerGUID: "+playerGUID);
				}
				return;
			}
			
			GroupMessage msg = new GroupMessage();
			msg.setDestModuleID(moduleString);
			msg.setMessageContent(data);
			msg.setMessageType(MessageTypes.S_ANALYSIS_PLAYER_REVENUE_AND_EXPENDITURE);
			MessageAction.sendGroupMessage(msg);
		} catch (Exception e) {
			if(log.isErrorEnabled()){
				log.error(GameUtility.getCurMethodName()+" can't send GroupMessage, moduleName: "+ModuleName.GROUP_ANALYSIS+" playerGUID: "+playerGUID, e);
			}
			return;
		}

	}
	
	/**
	 * 新增遊戲收入 
	 */
	public void addGameRevenue
	(
			GameNameWithHallType gameNameWithHallType,
			String playerBet,
			boolean isFormal,
			GUID orderID,
			boolean isUseCard
	)
	{
		
		BigDecimal playerBetBig = new BigDecimal(playerBet);
		
		GameRevenueAndExpenditureData REData = new GameRevenueAndExpenditureData();
		//如果暫存內沒有資料
		if(GameREMap.get(gameNameWithHallType) == null){
			if(isFormal)
				REData.setTotalBetFormal(playerBetBig);
			else
				REData.setTotalBetTrial(playerBetBig);
			
			GameREMap.put(gameNameWithHallType, REData);
		}
		//暫存內已有資料
		else {
			REData = GameREMap.get(gameNameWithHallType);
			if(isFormal){
				BigDecimal newValue = Helper.add(REData.getTotalBetFormal(), playerBetBig);
				GameREMap.get(gameNameWithHallType).setTotalBetFormal(newValue);
			}
			else{
				BigDecimal newValue = Helper.add(REData.getTotalBetTrial(), playerBetBig);
				GameREMap.get(gameNameWithHallType).setTotalBetTrial(newValue);
			}
				
		}
		
		//是使用卡片下注的話，把orderID記起來
		if(isUseCard){
			ArrayList<GUID> orderIDs = useCardBetOrderIDs.get(gameNameWithHallType);
			if(orderIDs == null){
				orderIDs = new ArrayList<GUID>();
				orderIDs.add(orderID);
				useCardBetOrderIDs.put(gameNameWithHallType, orderIDs);
			}
			else{
				if(orderIDs.contains(orderID)){
					if(log.isErrorEnabled()){
						log.error("there is a same orderID in useCardBetOrderIDs list, "+gameNameWithHallType.name());
					}
				}
				else{
					orderIDs.add(orderID);					
				}
			}
		}
	}
	
	/**
	 * 新增遊戲支出
	 */
	public void addGameExpenditure
	(
			GameNameWithHallType gameNameWithHallType,
			String payout,
			String payWay,
			boolean isFormal,
			GUID orderID
	)
	{
		
		BigDecimal payOutBig = new BigDecimal(payout);
		
		//無法判斷的支出
		if(!(payWay.equals(ExpenditurePayWay.PLAYER_SCORE) || payWay.equals(ExpenditurePayWay.JP_RELEASE))){
			if(log.isErrorEnabled()){
				log.error("Can't add Expenditure with a Wrong payWay: " + payWay);
			}
			return;
		}
		//體驗幣中JP的話就不計算
		if(payWay.equals(ExpenditurePayWay.JP_RELEASE) && !isFormal){
			if(log.isErrorEnabled()){
				log.error("someone get JP with TRIAL COIN!!! (it won't be calculated in game Revenue and Expenditure)");
			}
			return;
		}
		
		boolean isUseCardScore = false;
		
		GameRevenueAndExpenditureData REData = new GameRevenueAndExpenditureData();
		//如果暫存內沒有資料
		if(GameREMap.get(gameNameWithHallType) == null){
			//如果使用卡片的orderID裡面有紀錄
			if(useCardBetOrderIDs.get(gameNameWithHallType) != null && useCardBetOrderIDs.get(gameNameWithHallType).contains(orderID)){
				//就另外計算到使用卡片得分
				REData.setTotalCardScore(payOutBig);
				isUseCardScore = true;
			}
			else{
				//如果是玩家得分支出
				if(payWay.equals(ExpenditurePayWay.PLAYER_SCORE)){
					if(isFormal)
						REData.setTotalScoreFormal(payOutBig);
					else
						REData.setTotalScoreTrial(payOutBig);
				}
				//如果是JP支出(體驗幣不會中JP)
				else if(payWay.equals(ExpenditurePayWay.JP_RELEASE) && isFormal){
					REData.setTotalJP(payOutBig);
				}
			}
			
			GameREMap.put(gameNameWithHallType, REData);
		}
		//暫存內已有資料
		else {
			REData = GameREMap.get(gameNameWithHallType);
			
			//如果使用卡片的orderID裡面有紀錄
			if(useCardBetOrderIDs.get(gameNameWithHallType) != null && useCardBetOrderIDs.get(gameNameWithHallType).contains(orderID)){
				BigDecimal newValue = Helper.add(REData.getTotalCardScore(), payOutBig);
				GameREMap.get(gameNameWithHallType).setTotalCardScore(newValue);
				isUseCardScore = true;
			}
			else{
				//如果是玩家得分支出
				if(payWay.equals(ExpenditurePayWay.PLAYER_SCORE)){
					if(isFormal){
						BigDecimal newValue = Helper.add(REData.getTotalScoreFormal(), payOutBig);
						GameREMap.get(gameNameWithHallType).setTotalScoreFormal(newValue);
					}
					else{
						BigDecimal newValue = Helper.add(REData.getTotalScoreTrial(), payOutBig);
						GameREMap.get(gameNameWithHallType).setTotalScoreTrial(newValue);
					}
				}
				//如果是JP支出(體驗幣不會中JP)
				else if(payWay.equals(ExpenditurePayWay.JP_RELEASE) && isFormal){
					BigDecimal newValue = Helper.add(REData.getTotalJP(), payOutBig);
					GameREMap.get(gameNameWithHallType).setTotalJP(newValue);
				}
			}
		}
		
		//正式幣警示判斷
		if(!GameREMap.get(gameNameWithHallType).isFormalAlertAlready() && isFormal)
			doGameAlertJudgeMent(gameNameWithHallType, isFormal);
		//體驗幣警示判斷
//		else if(!GameREMap.get(gameNameWithHallType).isTrialAlertAlready() && !isFormal)
//			doAlertJudgeMent(gameNameWithHallType, isFormal);
		
		if(isUseCardScore){
			useCardBetOrderIDs.get(gameNameWithHallType).remove(orderID);
		}
	}
	
	/**
	 * 判斷遊戲達成警示條件與相關措施
	 */
	public void doGameAlertJudgeMent(GameNameWithHallType gameNameWithHallType, boolean isFormal){
		//如果達到警示差額就送訊息並寫警示LOG
		if(GameREMap.get(gameNameWithHallType).isDifferenceAlert(isFormal)){
			//發送警示
			sendAlertToSomewhere(isFormal);
			
			GameRevenueAndExpenditureAlertLogModel alertModel = new GameRevenueAndExpenditureAlertLogModel();
			alertModel.setRecordType(GameRevenueAndExpenditureAlertLogModel.ALERT_GAME_RECORD_TYPE);
			alertModel.setRecordName(gameNameWithHallType.toString());
			alertModel.setTotalBetFormal(GameREMap.get(gameNameWithHallType).getTotalBetFormal());
			alertModel.setTotalScoreFormal(GameREMap.get(gameNameWithHallType).getTotalScoreFormal());
			alertModel.setTotalJP(GameREMap.get(gameNameWithHallType).getTotalJP());
			alertModel.setTotalBetTrial(GameREMap.get(gameNameWithHallType).getTotalBetTrial());
			alertModel.setTotalScoreTrial(GameREMap.get(gameNameWithHallType).getTotalScoreTrial());
			
			if(isFormal){
				alertModel.setRecordReason(GameRevenueAndExpenditureAlertLogModel.FORMAL_COIN_REACH_ALERT_VALUE);
				alertModel.setAlertValue(GameRevenueAndExpenditureData.FORMAL_ALERT_DIFFERENCE);
				//設定已警示過標記
				GameREMap.get(gameNameWithHallType).setFormalAlertAlready(true);
			}
			else{
				alertModel.setRecordReason(GameRevenueAndExpenditureAlertLogModel.TRIAL_COIN_REACH_ALERT_VALUE);
				alertModel.setAlertValue(GameRevenueAndExpenditureData.TRIAL_ALERT_DIFFERENCE);
				//設定已警示過標記
				GameREMap.get(gameNameWithHallType).setTrialAlertAlready(true);
			}
			
			//寫DB
			SaveGameRevenueAndExpenditureLogAsync async = new SaveGameRevenueAndExpenditureLogAsync();
			async.setAlertModel(alertModel);
			getModule().addAsyncCommand(async);
		}
	}
	
	/**
	 * 增加玩家收入
	 */
	public void addPlayerRevenue(String playerGUID, String ScoreOrJP, boolean isJP){
		BigDecimal scoreOrJPBig = new BigDecimal(ScoreOrJP);
		
		PlayerRevenueAndExpenditureData REData = new PlayerRevenueAndExpenditureData();
		//如果MAP沒資料
		if(PlayerREMap.get(playerGUID) == null){
			if(isJP)
				REData.setTotalJPFormal(scoreOrJPBig);
			else
				REData.setTotalScoreFormal(scoreOrJPBig);
			
			PlayerREMap.put(playerGUID, REData);
		}
		else{
			REData = PlayerREMap.get(playerGUID);
			if(isJP){
				BigDecimal newValue = Helper.add(REData.getTotalJPFormal(), scoreOrJPBig);
				PlayerREMap.get(playerGUID).setTotalJPFormal(newValue);
			}
			else{
				BigDecimal newValue = Helper.add(REData.getTotalScoreFormal(), scoreOrJPBig);
				PlayerREMap.get(playerGUID).setTotalScoreFormal(newValue);
			}
		}
		
		//處理警示
		if(!PlayerREMap.get(playerGUID).isAlertAlready()){
			doPlayerAlertJudgement(playerGUID);
		}
	}
	
	/**
	 * 增加玩家支出 
	 */
	public void addPlayerExpenditure(String playerGUID, String bet){
		
		BigDecimal betBig = new BigDecimal(bet);
		
		PlayerRevenueAndExpenditureData REData = new PlayerRevenueAndExpenditureData();
		//如果MAP沒資料
		if(PlayerREMap.get(playerGUID) == null){
			REData.setTotalBetFormal(betBig);
			PlayerREMap.put(playerGUID, REData);
		}
		else{
			REData = PlayerREMap.get(playerGUID);
			
			BigDecimal newValue = Helper.add(REData.getTotalBetFormal(), betBig);
			REData.setTotalBetFormal(newValue);
		}
	}
	
	/**
	 * 判斷玩家達成警示條件與相關措施
	 */
	public void doPlayerAlertJudgement(String playerGUID){
		//如果達到警示差額就送訊息並寫警示LOG
		if(PlayerREMap.get(playerGUID).isAlertDifference()){
			
			//發送警示
			sendAlertToSomewhere(true);
			//設定以警示標記
			PlayerREMap.get(playerGUID).setAlertAlready(true);
			
			GameRevenueAndExpenditureAlertLogModel alertLogModel = new GameRevenueAndExpenditureAlertLogModel();
			alertLogModel.setRecordType(GameRevenueAndExpenditureAlertLogModel.ALERT_PLAYER_RECORD_TYPE);
			alertLogModel.setRecordName(playerGUID);
			alertLogModel.setTotalBetFormal(PlayerREMap.get(playerGUID).getTotalBetFormal());
			alertLogModel.setTotalScoreFormal(PlayerREMap.get(playerGUID).getTotalScoreFormal());
			alertLogModel.setTotalJP(PlayerREMap.get(playerGUID).getTotalJPFormal());
			alertLogModel.setRecordReason(GameRevenueAndExpenditureAlertLogModel.FORMAL_COIN_REACH_ALERT_VALUE);
			alertLogModel.setAlertValue(PlayerRevenueAndExpenditureData.PLAYER_ALERT_DIFFERENCE);
			
			//寫DB
			SaveGameRevenueAndExpenditureLogAsync async = new SaveGameRevenueAndExpenditureLogAsync();
			async.setAlertModel(alertLogModel);
			getModule().addAsyncCommand(async);
		}
	}
	
	/**
	 * 發送警示
	 */
	public void sendAlertToSomewhere(boolean isFormal){
		//還不知道要怎麼做
	}
	
	/**
	 * 將儲存的遊戲收支紀錄寫入DB
	 */
	public void SaveAllGameRevenueAndExpenditureLog(){
		if(GameREMap == null)
			return;
		for(Map.Entry<GameNameWithHallType, GameRevenueAndExpenditureData> entry : GameREMap.entrySet()){
			if(entry != null){
				SaveGameRevenueAndExpenditureLogAsync async = new SaveGameRevenueAndExpenditureLogAsync();
				GameRevenueAndExpenditureModel model = new GameRevenueAndExpenditureModel();
				GameRevenueAndExpenditureData data = entry.getValue();
				model.setGameNameWithHallType(entry.getKey().name());
				model.setGameNameWithHallTypeDesc(entry.getKey().getNameString());
				model.setTotalBetFormal(data.getTotalBetFormal());
				model.setTotalBetTrial(data.getTotalBetTrial());
				model.setTotalJP(data.getTotalJP());
				model.setTotalScoreFormal(data.getTotalScoreFormal());
				model.setTotalScoreTrial(data.getTotalScoreTrial());
				model.setTotalCardScore(data.getTotalCardScore());
				async.setModel(model);
				getModule().addAsyncCommand(async);
			}
		}
		
		//看一下有沒有殘留的orderID
		for(Entry<GameNameWithHallType, ArrayList<GUID>> entry : useCardBetOrderIDs.entrySet()){
			if(entry.getValue() != null && entry.getValue().size() > 0){
				if(log.isWarnEnabled()){
					log.warn(entry.getKey().name() + " has some orderIDs for useCardScore, amount:" + entry.getValue().size());
				}
			}
		}
	}
	
	/**
	 * 一段時間將遊戲收支紀錄暫存清除
	 */
	public void onTimeTriggerClearGameREMap(){
		GameREMap.clear();
		initGameREMap();
	}
	
	/**
	 * 一段時間將玩家收支紀錄暫存清除
	 */
	public void onTimeTriggerClearPlayerREMap(){
		if(PlayerREMap == null)
			return;
		else
			PlayerREMap.clear();
	}
	
	/**
	 * 送支出給統計module
	 * @param expenditureWay
	 * @param addValue
	 */
	public static void sendCoinExpenditureToAnalysis(CoinExpenditureWay coinExpenditureWay, BigDecimal addValue){
		if(coinExpenditureWay == null || Helper.isLessEqual(addValue, BigDecimal.ZERO)){
			return;
		}
		
		SCoinExpenditureData data = new SCoinExpenditureData();
		data.setCoinExpenditureWay(coinExpenditureWay.name());
		data.setAddValue(addValue);
		
		try {
			String destModuleID = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, coinExpenditureWay.name());
			if(destModuleID == null){
				return;
			}
			
			GroupMessage msg = new GroupMessage();
			msg.setMessageType(MessageTypes.S_ANALYSIS_ADD_COIN_EXPENDITURE);
			msg.setDestModuleID(destModuleID);
			msg.setMessageContent(data);
			MessageAction.sendGroupMessage(msg);
			
		}
		catch (Exception e) {
			if(log.isErrorEnabled()){
				log.error(GameUtility.getCurMethodName()+" can't send GroupMessage, moduleName: "+ModuleName.GROUP_ANALYSIS, e);
			}
			return;
		}
	}
	
	/**
	 * 檢查與新增C幣產出警示設定(除了遊戲館之外的)
	 */
	public void checkAndAddNewCoinExpenditureAlertSetting(){
		ArrayList<CoinExpenditureAlertSettingModel> settingsFromDB = AnalysisGroupDAO.getInstance().getAllCoinExpenditureAlertSetting();
		for(CoinExpenditureWay way : CoinExpenditureWay.values()){
			int count = 0;
			//檢查DB裡面全部的
			for(CoinExpenditureAlertSettingModel model : settingsFromDB){
				if(model.getCoinExpenditureWay() == way){
					break;
				}
				count ++;
			}
			//都沒找到的話就新增
			if(count == settingsFromDB.size()){
				AnalysisGroupDAO.getInstance().insertNewCoinExpenditureSetting(way);
				count = 0;
			}
		}
	}
	
	/**
	 * 載入所有警示設定
	 */
	public void loadAllCoinExpenditureSetting(boolean isInit){
		ArrayList<CoinExpenditureAlertSettingModel> settingsFromDB = AnalysisGroupDAO.getInstance().getAllCoinExpenditureAlertSetting();
		if(settingsFromDB == null || settingsFromDB.size() == 0){
			if(log.isErrorEnabled()){
				log.error("coin_expenditure_alert_setting table is empty");
			}
			return;
		}
		
		synchronized (CoinExpenditureMap) {
			//檢查DB裡面全部的
			for(CoinExpenditureAlertSettingModel model : settingsFromDB){
				CoinExpenditureWay way = model.getCoinExpenditureWay();
				//這邊LookUp方式要跟送消息進來的方式一樣
				String moduleLookUp = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, way.name());
				//自己module負責的的C幣產出項目才紀錄
				if(getModule().getModuleName().equals(moduleLookUp)){
					BigDecimal alertValue = model.getAlertValue();
					CoinExpenditureData data;
					if(isInit){
						data = new CoinExpenditureData(alertValue);
					}
					else{
						data = CoinExpenditureMap.get(way);
						data.setAlertValue(alertValue);
						data.setAlertAlready(false);
					}
					CoinExpenditureMap.put(way, data);
				}
			}
		}
	}
	
	/**
	 * 增加遊戲館收支之外的支出統計
	 * @param data
	 */
	public void addCoinExpenditure(SCoinExpenditureData data){
		
		//取得增加方式
		CoinExpenditureWay coinExpenditureWay = CoinExpenditureWay.getEnumByString(data.getCoinExpenditureWay());
		if(coinExpenditureWay == null){
			if(log.isErrorEnabled()){
				log.error(GameUtility.getCurMethodName()+" :coinExpenditureWay can't recognize by string: "+data.getCoinExpenditureWay());
			}
			return;
		}
		
		synchronized (CoinExpenditureMap) {
			//如果沒有記錄表示載入設定時可能有出錯
			if(CoinExpenditureMap.get(coinExpenditureWay) == null){
				if(log.isErrorEnabled()){
					log.error("can't find coinExpenditureWay: "+coinExpenditureWay+" in CoinExpenditureMap, something wrong at loadAllCoinExpenditureSetting");
				}
				return;
			}
			else{
				CoinExpenditureMap.get(coinExpenditureWay).addTotalValue(data.getAddValue());
			}
			
			doCoinExpenditureAlertJudgement(coinExpenditureWay);
		}

	}
	
	/**
	 * 判斷C幣產出警示標準
	 * @param coinExpenditureWay
	 */
	public void doCoinExpenditureAlertJudgement(CoinExpenditureWay coinExpenditureWay){
		//達到警示標準
		if(CoinExpenditureMap.get(coinExpenditureWay).isAlert()){
			//送警報
			sendAlertToSomewhere(true);
			
			//寫DB
			GameRevenueAndExpenditureAlertLogModel alertLogModel = new GameRevenueAndExpenditureAlertLogModel();
			alertLogModel.setRecordType(GameRevenueAndExpenditureAlertLogModel.ALERT_OTHER_COIN_PAY_RECORD_TYPE);
			alertLogModel.setRecordName(coinExpenditureWay.name());
			alertLogModel.setTotalBetFormal(BigDecimal.ZERO);
			alertLogModel.setTotalScoreFormal(CoinExpenditureMap.get(coinExpenditureWay).getTotalValue());
			alertLogModel.setTotalJP(BigDecimal.ZERO);
			alertLogModel.setRecordReason(GameRevenueAndExpenditureAlertLogModel.FORMAL_COIN_REACH_ALERT_VALUE);
			alertLogModel.setAlertValue(CoinExpenditureMap.get(coinExpenditureWay).getAlertValue());
			
			SaveGameRevenueAndExpenditureLogAsync async = new SaveGameRevenueAndExpenditureLogAsync();
			async.setAlertModel(alertLogModel);
			getModule().addAsyncCommand(async);
		}
	}
	
	/**
	 * 將儲存的遊戲收支紀錄寫入DB
	 */
	public void SaveAllCoinExpenditureLog(){
		if(CoinExpenditureMap == null || CoinExpenditureMap.size() == 0)
			return;
		for(Entry<CoinExpenditureWay, CoinExpenditureData> entry : CoinExpenditureMap.entrySet()){
			if(entry != null){
				CoinExpenditureWay way = entry.getKey();
				CoinExpenditureData data = entry.getValue();
				
				//不管有沒有大於0都要紀錄
				if(data != null){
					SaveGameRevenueAndExpenditureLogAsync async = new SaveGameRevenueAndExpenditureLogAsync();
					CoinExpenditureLogModel model = new CoinExpenditureLogModel();
					model.setCoinExpenditureWay(way);
					model.setCoinExpenditureWayDesc(way.getDesc());
					model.setExpenditure(data.getTotalValue());
					
					async.setCoinModel(model);
					getModule().addAsyncCommand(async);
				}
			}
		}
	}
	
	/**
	 * 一段時間將C幣支出紀錄暫存歸零
	 */
	public void setAllCoinExpenditureTotalValueToZero(){
		synchronized (CoinExpenditureMap) {
			if(CoinExpenditureMap == null || CoinExpenditureMap.size() == 0)
				return;
			else{
				for(Entry<CoinExpenditureWay, CoinExpenditureData> entry : CoinExpenditureMap.entrySet()){
					CoinExpenditureWay way = entry.getKey();
					CoinExpenditureMap.get(way).setToZero();
				}
			}
		}
	}
	
	/**
	 * 送給統計檢查寄送禮物操作間格時間
	 */
	public static void sendGiftSendingTimeCheckToAnalysis(GUID sessionID){
		GUID playerGUID = PlayerCommonInfoManager.getInstance().getPlayerGuidBySession(sessionID);
		if(playerGUID == null)
			return;
		
		try {
			String destModuleID = ConsistentGroupName.lookUp(ModuleName.GROUP_ANALYSIS, playerGUID);
			if(destModuleID == null){
				if(log.isErrorEnabled()){
					GameUtility.logErrorException(log, "lookUp result null, playerGUID: "+playerGUID);
				}
				return;
			}
			
			//取得發生時間
			long currentTime = TimeSys.currentTimeMills();
			SCheckSendGiftIntervalTimeData data = new SCheckSendGiftIntervalTimeData();
			data.setPlayerGUID(playerGUID.toString());
			data.setCheckTime(currentTime);
			
			GroupMessage msg = new GroupMessage();
			msg.setMessageType(MessageTypes.S_ANALYSIS_CHECK_SEND_GIFT_INTERVAL_TIME);
			msg.setDestModuleID(destModuleID);
			msg.setMessageContent(data);
			MessageAction.sendMessage(msg);
			
		} catch (Exception e) {
			if(log.isErrorEnabled()){
				log.error(GameUtility.getCurMethodName()+" can't send GroupMessage, moduleName: "+ModuleName.GROUP_ANALYSIS+" playerGUID: "+playerGUID, e);
			}
			return;
		}
	}
	
	/**
	 * 檢查玩家寄送禮物操作間隔
	 */
	public void checkPlayerSendGiftIntervalTime(SCheckSendGiftIntervalTimeData data){
		//取得資訊
		String playerGUID = data.getPlayerGUID();
		long checkTime = data.getCheckTime();
		
		//取得設定檔中的合法時間
		long legalTime = (Long) GameOperateAlertInfoUtil.SendGiftSetting.get(ActionType.SEND_GIFT_REQUEST);
		
		//沒這個人就幫他建立紀錄
		if(PlayerOPMap.get(playerGUID) == null){
			GameOperateData gameOperateData = new GameOperateData();
			gameOperateData.lastSendGiftTime = checkTime;
			PlayerOPMap.put(playerGUID, gameOperateData);
		}
		//有這個人就檢查
		else{
			//小於合法時間表示操作太頻繁了
			long diffTime = checkTime - PlayerOPMap.get(playerGUID).lastSendGiftTime;
			if(diffTime <= legalTime){
				//處理警示相關LOG
				GameOperateAlertLogModel alertLogModel = new GameOperateAlertLogModel();
				alertLogModel.setPlayerGUID(playerGUID);
				alertLogModel.setOperateType(OperateType.SEND_GIFT);
				alertLogModel.setActionType(ActionType.SEND_GIFT_REQUEST);
				alertLogModel.setDetail(String.format(ActionType.SEND_GIFT_REQUEST.getDetail(), diffTime, legalTime));
				alertLogModel.setDescription(ActionType.SEND_GIFT_REQUEST.getDescription());
				
				SaveAlertLogModelAsync async = new SaveAlertLogModelAsync();
				async.setAlertLogModel(alertLogModel);
				getModule().addAsyncCommand(async);
				
			}
			
			//更新最後時間
			PlayerOPMap.get(playerGUID).lastSendGiftTime = checkTime;
		}
	}
	
	/**
	 * 清除過期或是無效的操作紀錄
	 */
	public void cleanPlayerOPMap(){
		if(PlayerOPMap == null || PlayerOPMap.size() == 0)
			return;
		
		//操作當下的時間
		long currentTime = TimeSys.currentTimeMills();
		
		//每個玩家
		for(Entry<String, GameOperateData> entry : PlayerOPMap.entrySet()){
			String playerGUID = entry.getKey();
			GameOperateData opData = entry.getValue();
			
			//對每個行動檢查
			for(ActionType actionType : ActionType.values()){
				switch (actionType) {
				case SEND_GIFT_REQUEST: {
					long diffTime = currentTime - opData.lastSendGiftTime;
					if(diffTime > (Long) GameOperateAlertInfoUtil.SendGiftSetting.get(ActionType.SEND_GIFT_REQUEST)){
						opData.lastSendGiftTime = 0L;
					}
					
					break;
				}
					

				default:
					break;
				}
			}
			
			PlayerOPMap.put(playerGUID, opData);
			
			//如果都是初始狀態
			if(opData.isInitial()){
				PlayerOPMap.remove(playerGUID);
			}
				
		}
		
		if(PlayerOPMap.size() == 0)
			PlayerOPMap.clear();
	}
	
	/**
	 * 從ItemChangeParam送物品變化給統計
	 */
	public static void sendItemChangeToAnalysis(ItemChangeParam param){
		if(param == null)
			return;
		
		//只統計玩家的
		if(param.getUserType() != PlayerUserType.PLAYER)
			return;
		
		//沒有變化
		if(param.getChangeItemLister() == null)
			return;
		
		//ItemChangeParam取得的內容
		HashMap<String, ChangeItem> itemMap = param.getChangeItemLister().getItemMap();
		if(itemMap == null)
			return;
		
		//傳給統計的內容
		HashMap<String, Integer> changeItems = new HashMap<String, Integer>();
		
		for(ChangeItem changeItem : itemMap.values()){
			String itemIDWithRefineLevel = getItemIDWithRefineLV(changeItem.getItemId(), changeItem.getItemRefineLevel());
			int changeValue = changeItem.getChangeCount();
			
			if(changeItems.get(itemIDWithRefineLevel) == null)
				changeItems.put(itemIDWithRefineLevel, changeValue);
			else{
				int count = changeItems.get(itemIDWithRefineLevel);
				changeItems.put(itemIDWithRefineLevel, count + changeValue);
			}
			
		}
		
		//物品變化方式
		ItemChangeWay itemChangeWay = param.getChangeWay();
		sendItemChangeToAnalysis(itemChangeWay, changeItems);
	}
	
	/**
	 * 送給多個帳號物品統計
	 */
	public static void sendItemChangeToAnalysis(AccountItemChangeWay accountItemChangeWay, ArrayList<AccountItemModel> itemModels){
		if(itemModels == null || itemModels.size() == 0)
			return;
		
		HashMap<String, Integer> items = new HashMap<String, Integer>();
		
		for(AccountItemModel accountItemModel : itemModels){
			String itemIDWithRefineLV = getItemIDWithRefineLV(accountItemModel.getItemId(), 0);
			if(items.get(itemIDWithRefineLV) == null)
				items.put(itemIDWithRefineLV, 1);
			else{
				int count = items.get(itemIDWithRefineLV);
				items.put(itemIDWithRefineLV, count + 1);
			}
		}
		
		sendItemChangeToAnalysis(accountItemChangeWay, items);
	}
	
	/**
	 * 送單種物品變化給統計(角色)
	 */
	public static void sendItemChangeToAnalysis(ItemChangeWay itemChangeWay, String itemID, int refineLV, int amount){
		if(itemChangeWay == null || itemID == null || amount <= 0)
			return;
		
		String itemIDWithRefineLevel = getItemIDWithRefineLV(itemID, refineLV);
		
		HashMap<String, Integer> changeItems = new HashMap<String, Integer>();
		changeItems.put(itemIDWithRefineLevel, amount);
		sendItemChangeToAnalysis(itemChangeWay, changeItems);
	}
	
	/**
	 * 送單種物品變化給統計(帳號)
	 */
	public static void sendItemChangeToAnalysis(AccountItemChangeWay itemChangeWay, String itemID, int refineLV, int amount){
		if(itemChangeWay == null || itemID == null || amount <= 0)
			return;
		
		String itemIDWithRefineLevel = getItemIDWithRefineLV(itemID, refineLV);
		
		HashMap<String, Integer> changeItems = new HashMap<String, Integer>();
		changeItems.put(itemIDWithRefineLevel, amount);
		sendItemChangeToAnalysis(itemChangeWay, changeItems);
	}
	
	/**
	 * 送多種物品變化給統計(角色)
	 */
	public static void sendItemChangeToAnalysis(ItemChangeWay itemChangeWay, HashMap<String, Integer> changeItems){
		
		sendItemChange(false, null, itemChangeWay, changeItems);

	}
	
	/**
	 * 送多種物品變化給統計(帳號)
	 */
	public static void sendItemChangeToAnalysis(AccountItemChangeWay itemChangeWay, HashMap<String, Integer> changeItems){
		
		sendItemChange(true, itemChangeWay, null, changeItems);

	}
	
	/**
	 * changeItems的key是itemIDWithRefineLevel<br>
	 * 長這樣"itemID;1"
	 */
	private static void sendItemChange(boolean isAccountItem, AccountItemChangeWay accountItemChangeWay, ItemChangeWay itemChangeWay, HashMap<String, Integer> changeItems){
		//要交給analysis的消息 key:moduleID
		HashMap<String, SItemChangeData> messages = new HashMap<String, SItemChangeData>();
		
		//送消息的時候用lookUpByString，傳入itemID
		for(Entry<String, Integer> entry : changeItems.entrySet()){
			String itemIDWithRefineLevel = entry.getKey();
			
			//送到哪個analysis
			String[] lookup = entry.getKey().split(";");
			//尋找處理的Module
			String destModule = lookUpDestModule(lookup[0], lookup[1]);
			if(destModule == null)
				continue;
			
			int changeValue = entry.getValue();
			
			SItemChangeData data = messages.get(destModule);
			if(data == null){
				data = new SItemChangeData();
				if(isAccountItem){
					data.setItemChangeWay(accountItemChangeWay.name());
					data.setAccountItemChange(true);
				}
				else {
					data.setItemChangeWay(itemChangeWay.name());
					data.setAccountItemChange(false);
				}
			}
			
			HashMap<String, Integer> items = data.getChangeItems();
			if(items == null)
				items = new HashMap<String, Integer>();

			if(items.get(itemIDWithRefineLevel) == null){
				items.put(itemIDWithRefineLevel, changeValue);
			}
			else{
				int count = items.get(itemIDWithRefineLevel);
				items.put(itemIDWithRefineLevel, count + changeValue);
			}
			
			data.setChangeItems(items);
			
			messages.put(destModule, data);
		}
		
		for(Entry<String, SItemChangeData> entry : messages.entrySet()){
			GroupMessage msg = new GroupMessage();
			msg.setMessageType(MessageTypes.S_ANALYSIS_ITEM_CHANGE);
			msg.setDestModuleID(entry.getKey());
			msg.setMessageContent(entry.getValue());
			
			try {
				MessageAction.sendMessage(msg);
			} catch (Exception e) {
				if(log.isErrorEnabled()){
					log.error("sendItemChangeToAnalysis fail, itemChangeWay:"+entry.getValue().getItemChangeWay() + ", destModule:"+entry.getKey());
				}
				continue;
			}
		}

	}
	
	/**
	 * 取得道具的itemID + 精煉等級
	 */
	public static String getItemIDWithRefineLV(String itemID, int refineLV){
		return itemID + ";" + String.valueOf(refineLV);
	}
	
	/**
	 * 取得該道具要給的目標Module
	 */
	public static String lookUpDestModule(String itemID, String refineLV){
		if(itemID == null || refineLV == null)
			return null;
		
		ItemBasic itemBasic = ItemInfoManager.getInstance().getItem(itemID);
		if(itemBasic == null){
			if(log.isErrorEnabled()){
				log.error("itemBasic getDefaultIDWithRefinLV is null");
			}
			
			return null;
		}
		
		String lookupStr = itemBasic.getDefaultItemId() + ";" + refineLV;
		String destModule = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, lookupStr);
		if(destModule == null)
			return null;
		else return destModule;
	}
	
	/**
	 * 送非法回收的物品統計給analysis
	 */
	public static void sendInvalidItemChangeToAnalysis(boolean isAccountItem, String itemID, int refineLV, int count){
		if(itemID == null){
			return;
		}
		
		try {
			String changeWay = (isAccountItem) ? 
					AccountItemChangeWay.SUB_INVALID_ITEM_ID.name()
					:
					ItemChangeWay.SUB_INVALID_ITEM_ID.name();
								
			
			//非法物品都集中在這個module處理
			String destModule = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, changeWay);
			
			HashMap<String, Integer> items = new HashMap<String, Integer>();
			items.put(getItemIDWithRefineLV(itemID, refineLV), count);
			
			SItemChangeData data = new SItemChangeData();
			data.setItemChangeWay(changeWay);
			data.setChangeItems(items);
			data.setAccountItemChange(isAccountItem);

			GroupMessage msg = new GroupMessage();
			msg.setDestModuleID(destModule);
			msg.setMessageType(MessageTypes.S_ANALYSIS_ITEM_CHANGE);
			msg.setMessageContent(data);
			MessageAction.sendMessage(msg);
			
		} catch (Exception e) {
			if(log.isErrorEnabled()){
				log.error("sendInvalidItemChangeToAnalysis fail", e);
			}
			return;
		}
		
	}
	
	/**
	 * 收到所有物品變化統計
	 */
	public void revItemChange(boolean isAccountItem, AccountItemChangeWay accountChangeWay, ItemChangeWay changeWay, HashMap<String, Integer> changeItems){
		if(changeItems == null || changeItems.size() == 0)
			return;

		if(isAccountItem){
			if(accountChangeWay == null)
				return;
		}
		else {
			if(changeWay == null)
				return;
		}
		
		//非法物品就直接累計，不用嘗試讀他的資料
		if(isAccountItem){
			if(accountChangeWay.equals(AccountItemChangeWay.SUB_INVALID_ITEM_ID)){
				for(Entry<String, Integer> entry : changeItems.entrySet()){
					//非法物品還是要給他一個精煉等級，不然輸出LOG的時候會出錯
					String itemIDWithRefineLV = entry.getKey();
					int count = entry.getValue();
					
					ItemChangeData data = itemChangeDatas.get(itemIDWithRefineLV);
					if(data == null){
						data = new ItemChangeData();
					}
					data.addCount(accountChangeWay, count);
					
					itemChangeDatas.put(itemIDWithRefineLV, data);
				}
				
				return;
			}
		}
		else{
			if(changeWay.equals(ItemChangeWay.SUB_INVALID_ITEM_ID)){
				for(Entry<String, Integer> entry : changeItems.entrySet()){
					//非法物品還是要給他一個精煉等級，不然輸出LOG的時候會出錯
					String itemIDWithRefineLV = entry.getKey();
					int count = entry.getValue();
					
					ItemChangeData data = itemChangeDatas.get(itemIDWithRefineLV);
					if(data == null){
						data = new ItemChangeData();
					}
					data.addCount(changeWay, count);
					
					itemChangeDatas.put(itemIDWithRefineLV, data);
				}
				
				return;
			}
		}

		
//		System.out.println("!!!!!!!!!!rev item change!!!!!!!!!!!!\n itemChangeWay:"+changeWay.name());
		for(Entry<String, Integer> entry : changeItems.entrySet()){
			String[] itemIDWithRefineLevel = entry.getKey().split(";");
			String itemID = itemIDWithRefineLevel[0];
			int changeValue = entry.getValue();
			
			ItemBasic itemBasic = ItemInfoManager.getInstance().getItem(itemID);
			if(itemBasic == null){
				if(log.isErrorEnabled()){
					log.error("revItemChange itemBasic is null");
				}
				continue;
			}
			
			String itemName = itemBasic.getName();
			
			//是否是水晶或是C幣卡
			boolean isGold = false;
			boolean isCrystal = false;
			
			int goldCoin = 0;
			int crystalCount = 0;
			
			//是C幣卡就把面額set進來
			if(itemBasic.isGoldCoinCard()){
				ItemGoldCoinCard coinCard = (ItemGoldCoinCard) itemBasic;
				goldCoin = coinCard.getGoldCoin();
				isGold = true;
			}
			
			//是水晶卡就把數量set進來
			if(itemBasic.isCrystalCard()){
				ItemCrystalCard crystalCard = (ItemCrystalCard) itemBasic;
				crystalCount = crystalCard.getCrystalCount();
				isCrystal = true;
			}
			
			//C幣或水晶卡
			if(isGold || isCrystal){
				//用defaultID + 面額當作KEY，因為不同ID代表不同面額
				String defaultIDWithParvalue = null;
				if(isGold)
					defaultIDWithParvalue = itemBasic.getDefaultItemId() + ";" + goldCoin;
				if(isCrystal)
					defaultIDWithParvalue = itemBasic.getDefaultItemId() + ";" + crystalCount;
				
				ItemChangeData changeData = itemChangeDatas.get(defaultIDWithParvalue);
				if(changeData == null){
					changeData = new ItemChangeData();
					changeData.setItemName(itemName);
					changeData.setGoldCoin(String.valueOf(goldCoin));
					changeData.setCrystalCount(crystalCount);
					//用defaultIDWithParvalue當作KEY存入
					itemChangeDatas.put(defaultIDWithParvalue, changeData);
				}
				
				if(isAccountItem)
					changeData.addCount(accountChangeWay, changeValue);
				else changeData.addCount(changeWay, changeValue);
			}
			//一般道具
			else{
				String defaultIDWithRefineLV = (itemBasic.isAnalysisUseDefaultId()) ?
						itemBasic.getDefaultItemId() + ";" + itemIDWithRefineLevel[1]
						:
						itemID + ";" + itemIDWithRefineLevel[1];
				ItemChangeData changeData = itemChangeDatas.get(defaultIDWithRefineLV);
				if(changeData == null){
					changeData = new ItemChangeData();
					changeData.setItemName(itemName);
					changeData.setGoldCoin("0");
					changeData.setCrystalCount(0);
					//用defaultIDWithRefineLV當作KEY存入
					itemChangeDatas.put(defaultIDWithRefineLV, changeData);
				}
				
				if(isAccountItem)
					changeData.addCount(accountChangeWay, changeValue);
				else changeData.addCount(changeWay, changeValue);
			}

		}

//		printDEBUG();
	}
	
	/**
	 * 將物品變化統計存成ItemChangeCountModel，將會以這個列舉作為統計的依據<br>
	 * com.ms.service.module.analysisgroup.ItemCountWay<br>
	 * 原因是有些itemChangeWay實際上並沒有造成伺服器內物品總數的增減
	 */
	public void saveItemChangeCount(){
		
		ArrayList<ItemChangeCountModel> saveModels = new ArrayList<ItemChangeCountModel>();
		
		//對每個物品
		for(Entry<String, ItemChangeData> entry : itemChangeDatas.entrySet()){
			String[] key = entry.getKey().split(";");
			String itemID = key[0];
			int refineLevel = Integer.valueOf(key[1]);
			
			ItemChangeData changeData = entry.getValue();
			
			BigDecimal goldCoinBig = new BigDecimal(changeData.getGoldCoin());
			
			//處理每種ItemChangeWay
			HashMap<ItemChangeWay, Integer> counter = changeData.getCounter();
			for(Entry<ItemChangeWay, Integer> entry2 : counter.entrySet()){
				ItemChangeWay changeWay = entry2.getKey();
				int count = entry2.getValue();
				
				ItemCountWay countWay = ItemCountWay.getItemCountWayByItemChangeWay(changeWay);
				//如果不是我們要count的種類就continue
				if(countWay == null){
					continue;
				}
				
				//要儲存的MODEL
				ItemChangeCountModel model = new ItemChangeCountModel();
				model.setItemID(itemID);
				model.setItemName(changeData.getItemName());
				model.setCount(count);
				model.setGoldCoin(goldCoinBig.intValue());
				model.setCrystalCount(changeData.getCrystalCount());
				model.setItemChangeWay(changeWay.name());
				model.setItemCountWay(countWay.name());
				
				//一般物品這兩個值都是0
				if(Helper.isZero(goldCoinBig) && changeData.getCrystalCount() == 0)
					model.setRefineLevel(refineLevel);
				
				saveModels.add(model);
			}
			
			//處理每種AccountItemChangeWay
			HashMap<AccountItemChangeWay, Integer> accountCounter = changeData.getAccountCounter();
			for(Entry<AccountItemChangeWay, Integer> entry2 : accountCounter.entrySet()){
				AccountItemChangeWay changeWay = entry2.getKey();
				int count = entry2.getValue();
				
				ItemCountWay countWay = ItemCountWay.getItemCountWayByAccountItemChangeWay(changeWay);
				//如果不是我們要count的種類就continue
				if(countWay == null){
					continue;
				}
				
				//要儲存的MODEL
				ItemChangeCountModel model = new ItemChangeCountModel();
				model.setItemID(itemID);
				model.setItemName(changeData.getItemName());
				model.setCount(count);
				model.setGoldCoin(goldCoinBig.intValue());
				model.setCrystalCount(changeData.getCrystalCount());
				model.setItemChangeWay(changeWay.name());
				model.setItemCountWay(countWay.name());
				
				//一般物品這兩個值都是0
				if(Helper.isZero(goldCoinBig) && changeData.getCrystalCount() == 0)
					model.setRefineLevel(refineLevel);
				
				saveModels.add(model);
			}
		}
		
		//清空資料
		this.itemChangeDatas.clear();

		//寫入DB
		if(saveModels.size() > 0){
			SaveItemChangeCountAsync async = new SaveItemChangeCountAsync();
			async.setItemChangeCountModels(saveModels);
			module.addAsyncCommand(async);
		}
	}
	
	/**
	 * 送詳細的C幣卡資訊給統計
	 */
	public static void sendCoinCardDetailToAnalysis
	(
			ItemChangeWay createWay,
			String createGameType,
			String createHallType,
			int coinCardParValue
	)
	{
		if(coinCardParValue <= 0){
			//如果面額不合理還是照送，不擋住
			if(log.isWarnEnabled()){
				log.warn("there is a coin card parValue is small than zero!!!");
			}
		}
		
		SUsedCoinCardDetailData data = new SUsedCoinCardDetailData();
		data.setBatch(false);
		data.setParValue(coinCardParValue);
		data.setCreateWay(createWay);
		data.setCreateGameType(createGameType);
		data.setCreateHallType(createHallType);
		
		try {
			//根據面額分配
			String destModule = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, String.valueOf(coinCardParValue));
			
			GroupMessage msg = new GroupMessage();
			msg.setDestModuleID(destModule);
			msg.setMessageType(MessageTypes.S_ANALYSIS_USED_COIN_CARD_DETAIL);
			msg.setMessageContent(data);
			MessageAction.sendMessage(msg);
			
		} catch (Exception e) {
			if(log.isErrorEnabled()){
				log.error("sendCoinCardDetailToAnalysis fail", e);
			}
			return;
		}
	}
	
	/**
	 * 送詳細的C幣卡資訊給統計(批量)
	 */
	public static void sendBatchCoinCardDetailToAnalysis(ArrayList<Item> coinCards){
		if(coinCards == null || coinCards.size() == 0){
			return;
		}
		
		HashMap<String, ArrayList<Item>> sendDatas = new HashMap<String, ArrayList<Item>>();
		for(Item item : coinCards){
			ItemBasic itemBasic = ItemInfoManager.getInstance().getItem(item.getItemId());
			
			//是C幣卡才送
			if(itemBasic instanceof ItemGoldCoinCard){
				ItemGoldCoinCard goldCoinCard = (ItemGoldCoinCard)itemBasic;
				
				String destModule = ConsistentGroupName.lookUpByString(ModuleName.GROUP_ANALYSIS, String.valueOf(goldCoinCard.getGoldCoin()));
				if(!GameUtility.isHaveString(destModule)){
					if(log.isErrorEnabled()){
						log.error("can't find destModule for coin card parValue="+goldCoinCard.getGoldCoin());
					}
					continue;
				}
				
				ArrayList<Item> cards = sendDatas.get(destModule);
				if(cards == null){
					cards = new ArrayList<Item>();
					sendDatas.put(destModule, cards);
				}
				cards.add(item);
			}
			
		}
		
		for(Entry<String, ArrayList<Item>> entry : sendDatas.entrySet()){
			try {
				String destModule = entry.getKey();
				
				SUsedCoinCardDetailData data = new SUsedCoinCardDetailData();
				data.setBatch(true);
				data.setCoinCards(entry.getValue());
				
				GroupMessage msg = new GroupMessage();
				msg.setDestModuleID(destModule);
				msg.setMessageType(MessageTypes.S_ANALYSIS_USED_COIN_CARD_DETAIL);
				msg.setMessageContent(data);
				MessageAction.sendMessage(msg);
				
			} catch (Exception e) {
				if(log.isErrorEnabled()){
					log.error("sendBatchCoinCardDetailToAnalysis fail", e);
				}
				continue;
			}
		}
		
	}
	
	/**
	 * 收到C幣卡使用細節
	 */
	public void revUsedCoinCardDetail(SUsedCoinCardDetailData data){

		//是批量的資訊
		if(data.isBatch()){
			for(Item item : data.getCoinCards()){
				ItemBasic itemBasic = ItemInfoManager.getInstance().getItem(item.getItemId());
				ItemGoldCoinCard goldCoinCard = (ItemGoldCoinCard) itemBasic;
				
				int parValue = goldCoinCard.getGoldCoin();
				
				UsedCoinCardData usedCoinCardData = usedCoinCardDetailMap.get(parValue);
				if(usedCoinCardData == null){
					usedCoinCardData = new UsedCoinCardData();
					usedCoinCardDetailMap.put(parValue, usedCoinCardData);
				}
				
				usedCoinCardData.add(item.getCreateItemWay(), item.getCreateGameType(), item.getCreateHallType());
			}
		}
		//是單一的資訊
		else{
			int parValue = Integer.valueOf(data.getParValue());
			
			UsedCoinCardData usedCoinCardData = usedCoinCardDetailMap.get(parValue);
			if(usedCoinCardData == null){
				usedCoinCardData = new UsedCoinCardData();
				usedCoinCardDetailMap.put(parValue, usedCoinCardData);
			}
			
			usedCoinCardData.add(data.getCreateWay(), data.getCreateGameType(), data.getCreateHallType());
		}
	}
	
	/**
	 * 寫入DB所有的C幣卡使用細節
	 */
	public void saveUsedCoinCardDetailLogs(){
		if(usedCoinCardDetailMap.size() == 0){
			return;
		}
		
		ArrayList<UsedCoinCardDetailLogModel> logModels = new ArrayList<UsedCoinCardDetailLogModel>();
		for(Entry<Integer, UsedCoinCardData> entry : usedCoinCardDetailMap.entrySet()){
			ArrayList<UsedCoinCardDetailLogModel> logs = entry.getValue().getLogModels(entry.getKey());
			logModels.addAll(logs);
		}
		
		//清空紀錄
		usedCoinCardDetailMap.clear();
		
		SaveUsedCoinCardDetailAsync async = new SaveUsedCoinCardDetailAsync();
		async.setLogModels(logModels);
		module.addAsyncCommand(async);
	}
	
//	private void printDEBUG(){
//		for(Entry<String, ItemChangeData> entry : itemChangeDatas.entrySet()){
//			System.out.println("==========\nitemID:"+entry.getKey());
//			for(Entry<ItemChangeWay, Integer> entry2 : entry.getValue().getCounter().entrySet()){
//				System.out.println("ItemChangeWay:"+entry2.getKey().name()+", count:"+entry2.getValue());
//			}
//			for(Entry<AccountItemChangeWay, Integer> entry2 : entry.getValue().getAccountCounter().entrySet()){
//				System.out.println("ItemChangeWay:"+entry2.getKey().name()+", count:"+entry2.getValue());
//			}
//		}
//	}
}
